package com.abso.mp3tunes.locker.core.services;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.SimpleHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.lang.ObjectUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;

import com.abso.mp3tunes.locker.core.LockerServiceException;

/**
 * An abstract base class for MP3tunes Locker services.
 */
public abstract class AbstractLockerService {

	/** HTTP connection default time-out (in milliseconds). */
	public static final int DEFAULT_TIMEOUT = 10000;

	/**
	 * The token which uniquely identifies and authenticates all entities using
	 * API's to communicate with MP3tunes.com
	 */
	private String partnerToken;

	/** The current time-out (default = DEFAULT_TIMEOUT). */
	private int timeout = DEFAULT_TIMEOUT;

	/**
	 * Constructs a new service.
	 * 
	 * @param partnerToken
	 *            the partner token, which uniquely identifies and authenticates
	 *            all entities using API's to communicate with MP3tunes.com
	 */
	public AbstractLockerService(String partnerToken) {
		this.partnerToken = partnerToken;
	}

	/**
	 * Sets the timeout.
	 * 
	 * @param timeout
	 *            the timeout.
	 * @see #DEFAULT_TIMEOUT
	 */
	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}

	/**
	 * Returns the partner token.
	 * 
	 * @return the partner token, which uniquely identifies and authenticates
	 *         all entities using API's to communicate with MP3tunes.com
	 */
	public String getPartnerToken() {
		return partnerToken;
	}

	/**
	 * Executes an authentication method, using a SSL-based HTTP connection.
	 * 
	 * @param methodName
	 *            the name of the method.
	 * @param params
	 *            the set of parameters being appended to the secure request.
	 * @return the resulting XML document.
	 * @throws LockerServiceException
	 */
	protected Document executeAuthentication(String methodName, Map params)
			throws LockerServiceException {
		return execute("shop", true, methodName, params);
	}

	/**
	 * Executes a general method, using a standard HTTP connection.
	 * 
	 * @param methodName
	 *            the name of the method.
	 * @param params
	 *            the set of parameters being appended to the request.
	 * @return the resulting XML document.
	 * @throws LockerServiceException
	 */
	protected Document executeGeneral(String methodName, Map params)
			throws LockerServiceException {
		Document doc = execute("ws", false, methodName, params);
		return doc;
	}

	/**
	 * Executes a REST method.
	 * 
	 * @param subDomain
	 *            the name of the <code>mp3tunes.com</code> subdomain (e.g.
	 *            <code>shop</code>, <code>ws</code>).
	 * @param secureRequest
	 *            if <code>true</code>, a secure HTTP request is performed.
	 * @param methodName
	 *            the name of the method.
	 * @param params
	 *            the set of parameters being appended to the secure request.
	 * @return the resulting XML document.
	 * @throws LockerServiceException
	 */
	protected Document execute(String subDomain, boolean secureRequest,
			String methodName, Map params) throws LockerServiceException {
		StringBuffer url = new StringBuffer();
		url.append(secureRequest ? "https://" : "http://");
		url.append(subDomain);
		url.append(".mp3tunes.com/api/v1/");
		url.append(methodName);
		url.append('?');
		for (Iterator i = params.entrySet().iterator(); i.hasNext();) {
			Map.Entry param = (Map.Entry) i.next();
			try {
				url.append(ServiceUtils.encodeURLParameter(ObjectUtils
						.toString(param.getKey())));
			} catch (UnsupportedEncodingException e) {
				throw new LockerServiceException("Unable to encode parameter: "
						+ param.getKey(), e);
			}
			url.append('=');
			try {
				url.append(ServiceUtils.encodeURLParameter(ObjectUtils
						.toString(param.getValue())));
			} catch (UnsupportedEncodingException e) {
				throw new LockerServiceException("Unable to encode parameter: "
						+ param.getValue(), e);
			}
			url.append('&');
		}
		url.append("output=xml&partner_token=");
		try {
			url.append(ServiceUtils.encodeURLParameter(partnerToken));
		} catch (UnsupportedEncodingException e) {
			throw new LockerServiceException("Unable to encode parameter: "
					+ partnerToken, e);
		}
		SimpleHttpConnectionManager connMgr = new SimpleHttpConnectionManager();
		connMgr.getParams().setConnectionTimeout(timeout);
		connMgr.getParams().setSoTimeout(timeout);
		connMgr.getParams().setParameter(HttpMethodParams.USER_AGENT,
				"MP3tunes Eclipse plug-in (http://abso.freehostia.com)");
		HttpMethodBase httpMethod = new GetMethod(url.toString());
		HttpClient httpClient = new HttpClient(connMgr);
		try {
			httpClient.executeMethod(httpMethod);
			SAXReader reader = new SAXReader();
			InputSource source = new InputSource(httpMethod
					.getResponseBodyAsStream());
			source.setEncoding("UTF-8"/* httpMethod.getResponseCharSet() */);
			return reader.read(source);
		} catch (HttpException e) {
			throw new LockerServiceException("Unable to perform request: "
					+ e.getMessage(), e);
		} catch (IOException e) {
			throw new LockerServiceException("Unable to perform request: "
					+ e.getMessage(), e);
		} catch (DocumentException e) {
			throw new LockerServiceException(
					"Unable to parse response of request: " + e.getMessage(), e);
		} finally {
			httpMethod.releaseConnection();
		}
	}

}
